/**
 * 内存管理系统初始化前期的内存分配工具。
 */
#ifndef _AHAMOLETT_EARLY_ALLOC_H_
#define _AHAMOLETT_EARLY_ALLOC_H_

#include <mm/early_alloc_types.h>

/**
 * @brief 初始化前期内存分配器。
 * 
 * 在完成对内存管理系统的初始化，并最终将其启动之前，仍有对内存分配的需求，此时就依靠前期内存
 * 分配器满足该需求。
 * 
 * 前期内存分配器的基本粒度为内存页，并利用色位图来记录内存页的分配情况。前期内存分配器的功能很
 * 原始，仅能使用一块连续的内存区域，且效率不高，其存在仅仅为协助初始化完整的内存管理系统。所以
 * 操作系统需要尽快完成内存管理系统的初始化，并让其接管整个物理内存。
 * 
 * 该函数会从给定的内存区域内划分一部分用于储存状态信息。
 * 
 * @param area_page_start 被接管内存区域的首个内存页页号。
 * @param area_page_end 第一个不在被接管内存区域内的内存页页号。
 * @param free_page_start 区域内第一个空闲的内存页页号。
 * @param free_page_end 第一个不在空闲区域内的内存页页号。
 * @param page_shift 从内存页页号到内存地址所需要左移的位数。
 * 
 * @return int 初始化成功返回0，失败返回-1。
 */
extern int early_alloc_init(
    unsigned long area_page_start,
    unsigned long area_page_end,
    unsigned long free_page_start,
    unsigned long free_page_end,
    unsigned long page_shift
);

/**
 * @brief 清除最早分配的内存页的伙伴系统标志。
 * 
 * 由于前期内存分配器启动于页结构体分配之前，所以在页结构体得到初始化后，需要依靠该接口重新将
 * 已经分配的内存页中伙伴系统的标志清除。
 */
extern void __mark_earliest_allocated_pfn(void);

/**
 * @brief 用前期内存分配器分配内存页。
 * 
 * 在前期内存分配器的色位图中寻找给定阶数的连续的空闲内存页，将这些内存页对应的位全部置位，并
 * 返回得到的内存区域的首地址。
 * 
 * @param order 需要分配连续内存页页数的阶数，即需要分配 2^order 个连续的内存页。
 * @param order_aligned 作为一个布尔变量。为真时内存区域需要按阶数对齐。
 * 
 * @return void* 分配成功返回分配到内存区域虚拟地址的首地址，失败返回NULL。
 */
extern void *early_pages_alloc(unsigned long order, int order_aligned);


/**
 * @brief 根据需要的页数用前期内存分配器分配内存页。
 * 
 * @param num 需要分配的连续的内存页数。
 * 
 * @return void* 分配成功返回分配到内存区域的虚拟地址首地址，失败返回NULL。
 */
extern void *early_pages_alloc_by_num(unsigned long num);


/**
 * @brief 在前期内存分配器中释放内存页。
 * 
 * 在前期内存分配器的色位图里将给定内存区域所对应的比特位清除，下次分配内存页时就可以再被分配。
 * 输入的起始地址为需要释放的内存区域的首地址，该函数会从该地址所在的内存页开始，释放 nums 个
 * 连续的内存页。
 * 
 * 该函数会将给定起始地址所在的整个内存页释放，且不会检查需要被释放的内存页是否全部被释放，仅仅
 * 将给定范围内的内存页在前期内存分配器的色位图中所对应的位清除，使其能够再次被分配。
 * 
 * @param page_beg 进行释放的内存区域虚拟地址的首地址。
 * @param nums 进行释放的内存区域所包含的内存页数量。
 */
extern void early_pages_free(void *page_beg, unsigned long nums);

/**
 * @brief 尝试分配指定范围内的内存页
 * 
 * @param page_start 指定范围的第一个内存页页号。
 * @param pages_num 指定范围的连续内存页页数。
 * 
 * @return int 分配成功返回0，失败返回-1。
 */
int early_pages_alloc_specific(
    unsigned long page_start, 
    unsigned long pages_num
);

/**
 * @brief 用前期内存分配器分配小块内存区域。
 * 
 * 在完整的内存管理系统初始化完成以前，为用户分配小于一个内存页的内存区域。该函数分配的内存总是
 * 按照8字节对齐，且只能分配不能释放。
 * 
 * @param size 需要分配的内存区域大小。
 * 
 * @return void* 分配成功返回分配到内存区域的首地址，失败返回NULL。
 */
extern void *early_alloc(size_t size);

/**
 * @brief 获得下一个连续的空闲内存页区域。
 * 
 * @param last_pages_end 第一个不在上一次遍历道德空闲区域的内存页页号。
 * @param[out] page_start 返回遍历到的空闲区域的首个内存页页号。
 * @param[out] pages_num 返回遍历到的空闲区域所包含的连续的内存页数量。
 * 
 * @return int 没有遍历完返回1，已结遍历完返回0。
 */
extern int early_alloc_next_free_area(
    unsigned long last_pages_end,
    unsigned long *page_start,
    unsigned long *pages_num
);

/**
 * @brief 遍历前期内存分配器当前所有空闲的内存页区域页号。
 * 
 * 对 for 循环的封装，作用是遍历被前期内存分配器管理的内存区域内所有的连续空闲内存页。
 * 
 * @param pos unsigned long 作为游标遍历空闲的内存区域。
 * @param page_start unsigned long 返回遍历到的空闲区域的首个内存页页号。
 * @param pages_num unsigned long 返回遍历到的空闲区域所包含的连续的内存页数量。
 */
#define for_each_early_free_area(pos, page_start, pages_num)    \
    for (   (pos) = 0;  \
            early_alloc_next_free_area(pos, &(page_start), &(pages_num));   \
            (pos) = (page_start) + (pages_num)  \
        )

/**
 * @brief 释放掉前期内存分配器保留给小块内存分配的完整的内存页。
 * 
 * 前期内存分配器在为用户分配小块内存以前，会事先申请一个或多个完整的内存页。申请到的内存区域
 * 一部分会被分配给用户，另一部分会保留起来用于下次的小块内存分配。
 * 
 * 该函数的作用是将前期内存分配器保留起来的内存区域按内存页的页框对齐，并释放掉。
 * 
 * @return unsigned long 返回被释放掉的内存页数量。
 */
extern unsigned long early_alloc_block_compact(void);

/**
 * @brief 禁用前期内存分配器。
 * 
 * 在完整的内存管理系统启用以前，需要禁用前期内存分配器，保证整个系统中只有一个内存管理系统。
 * 
 * @return int 禁用成功返回0，失败返回-1 。
 */
extern int early_alloc_disable(void);


#endif